package com.transfiguro.converters;

import ch.qos.logback.core.StopWatch;
import com.transfiguro.datamodel.Statement;
import com.transfiguro.datamodel.csmodels.CSMethod;
import com.transfiguro.datamodel.csmodels.CSVariable;
import com.transfiguro.datamodel.javamodels.Method;
import com.transfiguro.datamodel.javamodels.Variable;
import com.transfiguro.exceptions.ConversionFailedException;
import com.transfiguro.utilities.FileUtilities;
import com.transfiguro.utilities.MappingXMLParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;

import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

/**
 * An implementation of {@link Converter}. This class provides methods to convert a
 * {@link Method} to {@link CSMethod}
 */
public class MethodConverter implements Converter {
    private final Logger logger = LoggerFactory.getLogger(MethodConverter.class);
    private Method method;
    private CSMethod csMethod;
    private File methodXMLMapping;
    private static MappingXMLParser xmlParser;
    private StatementConverter statementConverter;

    /**
     * Constructor
     *
     * @param model  The <code>Method</code> to be converted
     */
    public MethodConverter(final Method model) {
        method = model;
        csMethod = new CSMethod();
        statementConverter = StatementConverter.getStatementConverter();
        init();
    }

    /**
     * Initializer for various private variables
     */
    private void init() {
        final ClassPathResource resource = new ClassPathResource("mappingxmls/BaseMethodMapping.xml");
        final InputStream stream = ClassLoader.getSystemResourceAsStream("mappingxmls/BaseMethodMapping.xml");
        final File tempFile = new File(".", "BaseMethodMapping.xml");
        FileUtilities.writeTempFile(stream, tempFile);
        methodXMLMapping = tempFile;
        xmlParser = new MappingXMLParser(methodXMLMapping);
    }

    /**
     * This method performs the conversion from <code>Method</code> to <code>CSMethod</code>
     *
     * @return  <tt>True</tt> if conversion is successful
     * @throws ConversionFailedException  if there is an error in conversion
     */
    public boolean convert() throws ConversionFailedException {
        final StopWatch stopWatch = new StopWatch("MethodConversionTimer");
        boolean result = true;
        List<Statement> csStatements = new ArrayList<Statement>();
        logger.info("Method: {}", method.getMethodName());
        if (xmlParser.requiresConversion("returnType", method.getReturnType().trim())) {
            csMethod.setReturnType(xmlParser.getTo("returnType", method.getReturnType()));
        } else {
            csMethod.setReturnType(method.getReturnType());
        }

        if (xmlParser.requiresConversion("methodName", method.getMethodName().trim())) {
            csMethod.setMethodName(xmlParser.getTo("methodName", method.getMethodName()));
        } else {
            csMethod.setMethodName(method.getMethodName());
        }

        if (Modifier.toString(method.getAccessModifier()) != "") {
            StringBuilder modifier = new StringBuilder();
            for (final String mod : Modifier.toString(method.getAccessModifier()).split(" "))
                if (xmlParser.requiresConversion("accessModifier", mod.trim())) {
                    modifier.append(xmlParser.getTo("accessModifier", mod)).append(" ");
                } else {
                    modifier.append(mod).append(" ");
                }
            csMethod.setAccessModifier(modifier.toString().trim());
        }

        csMethod.setInputParameters(getConvertedInputParams(method.getInputParameters()));
        logger.info("Statements for {}: {}", method.getMethodName(), method.getStatements().toString());
        for (final Statement statement : method.getStatements()) {
            csMethod.addStatement(statementConverter.convert(statement));
        }
        stopWatch.stop();
        csMethod.setConversionTime(stopWatch.getResultInSeconds());

        logger.info("Converted CS Statements: \n{}", csStatements.toString());
        //csMethod.setMethodBody(method.getMethodBody());
        return result;
    }

    /**
     * This method converts a given list of {@link Variable}s to a list of {@link CSVariable}s
     *
     * @param inputParams  The list of <code>Variable</code>s to be converted
     * @return  The list of converted <code>CSVariable</code>s
     */
    private List<CSVariable> getConvertedInputParams(final List<Variable> inputParams) {
        List<CSVariable> result = new ArrayList<CSVariable>();
        try {
            for (final Variable param : inputParams) {
                final VariableConverter converter = new VariableConverter(param);
                converter.convert();
                result.add(converter.getCsVariable());
            }
        } catch (final ConversionFailedException e) {
            logger.debug("Exception: {}", e.getMessage());
        }
        return result;
    }

    /**
     * Getter for <code>csMethod</code>
     *
     * @return  The <code>csMethod</code> created by converting
     *         the given <code>Method</code>
     */
    public CSMethod getCsMethod() {
        return csMethod;
    }
}
